package com.zhtools.excel.export;

import com.zhtools.LoggerUtils;
import com.zhtools.excel.Column;
import com.zhtools.excel.Formater;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class SimpleExcelExport<T> implements ExcelExport<T> {
    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleExcelExport.class);
    private static final String MULTITHREADEXPORT_OVER_INFO = "多线程写入Excel完成!总共写入{}条数据!";
    private static final String MULTITHREADEXPORT_ONEROW_INFO = "线程{}写入Excel第{}记录!";

    private static final String EMPTY_STRING = "";
    private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final int FIXEDTHREDS = 15;
    private static final ExecutorService TASKEXECUTORSERVICE = Executors.newFixedThreadPool(FIXEDTHREDS);
    private static final String LOCK = "LOCK";

    private String getFieldValue(Column column, T t) {
        String result = EMPTY_STRING;
        String geterMethodName = EMPTY_STRING;
        try {
            geterMethodName = column.geterMethodName();
            Method geterMethod;
            Object val = null;
            try {
                geterMethod = t.getClass().getMethod(geterMethodName, null);
                val = geterMethod.invoke(t, null);
                if (val == null) {
                    return result;
                }
            } catch (NoSuchMethodException e) {
                LOGGER.warn("没有找到属性{}匹配的geter方法{}", column.getField(), geterMethodName);
            }
            Formater format = column.getFormater();
            if (format != null) {
                result = format.format(t);
            } else {
                if (val instanceof Date) {
                    SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_PATTERN);
                    result = dateFormat.format(val);
                } else if (val instanceof BigDecimal) {
                    BigDecimal decimalVal = (BigDecimal) val;
                    result = decimalVal.toString();
                } else {
                    result = String.valueOf(val);
                }
            }
        } catch (InvocationTargetException e) {
            LOGGER.error("执行方法{}异常", geterMethodName);
        } catch (Exception e) {
            LOGGER.error("获取属性{}值异常", column.getField());
        }
        return result;
    }

    private void setDefaultValue(Column column, Cell cell) {
        if (StringUtils.isEmpty(column.getDefaultValue())) {
            cell.setCellValue(EMPTY_STRING);
        } else {
            cell.setCellValue(column.getDefaultValue());
        }
    }

    private List<Column> getHeaders(Field[] fields) {
        List<Column> headers = new ArrayList<>();
        for (Field field : fields) {
            headers.add(new Column(field.getName(), field.getName(), EMPTY_STRING));
        }
        return headers;
    }

    private List<Column> getHeaders() {
        List<Column> headers = createHeaders();
        if (headers == null || headers.size() == 0) {
            Class<T> clazz = getClazz();
            Field[] fields = clazz.getDeclaredFields();
            headers = getHeaders(fields);
        }
        return headers;
    }

    private int createHeader(XSSFSheet sheet, List<Column> headers) {
        int lastRowIndex = 0;
        if (headers == null || headers.size() == 0) {
            return lastRowIndex;
        }

        XSSFRow headRow = sheet.createRow(lastRowIndex);
        for (int i = 0; i < headers.size(); i++) {
            Column header = headers.get(i);
            headRow.createCell(i).setCellValue(StringUtils.isEmpty(header.getName()) ? EMPTY_STRING : header.getName());
        }
        return lastRowIndex + 1;
    }

    private int writeOneRow(T rowData, List<Column> columns, XSSFSheet sheet, int lastRowIndex) {
        XSSFRow dataRow = sheet.createRow(lastRowIndex);
        for (int i = 0; i < columns.size(); i++) {
            Cell cell = dataRow.createCell(i);
            Column column = columns.get(i);
            String val = getFieldValue(column, rowData);
            if (StringUtils.isEmpty(val)) {
                setDefaultValue(column, cell);
            } else {
                cell.setCellValue(val);
            }
        }
        return lastRowIndex + 1;
    }

    private Class<T> getClazz() {
        Class<T> clazz = null;
        Class c = this.getClass();
        Type t = c.getGenericSuperclass();
        if (t instanceof ParameterizedType) {
            Type[] p = ((ParameterizedType) t).getActualTypeArguments();
            clazz = (Class<T>) p[0];
        }
        return clazz;
    }

    /**
     * 创建导出标题
     *
     * @return
     */
    protected List<Column> createHeaders() {
        return null;
    }

    /**
     * 释放资源
     */
    private void closeResources(OutputStream ouputStream) {
        if (ouputStream != null) {
            try {
                ouputStream.flush();
                ouputStream.close();
            } catch (IOException e) {
                LOGGER.error("关闭资源异常");
            }
        }
    }

    /**
     * 写入Excel
     */
    private void writeExcel(OutputStream ouputStream, XSSFWorkbook workBook) {
        try {
            workBook.write(ouputStream);
        } catch (IOException e) {
            LOGGER.error("写入流异常", e);
        } finally {
            closeResources(ouputStream);
        }
    }

    /**
     * 写入列表
     */
    private void writeList(List<T> datas, XSSFSheet sheet, List<Column> columns, int lastRowIndex) {
        if (datas == null || datas.size() == 0) {
            return;
        }
        for (T t : datas) {
            lastRowIndex = writeOneRow(t, columns, sheet, lastRowIndex);
        }
    }

    /**
     * 写入分页
     */
    private void writePage(PageExport<T> pageExport, XSSFSheet sheet, int lastRowIndex, List<Column> columns) {
        Long totalPages = pageExport.getTotalPage();
        if (totalPages != null && totalPages.longValue() > 0) {
            for (long page = 1; page <= totalPages.longValue(); page++) {
                Long start = Long.valueOf((page - 1L) * pageExport.getPageSize());
                List<T> pageDatas = pageExport.query(start);
                if (pageDatas != null && pageDatas.size() > 0) {
                    for (int i = 0; i < pageDatas.size(); i++) {
                        T t = pageDatas.get(i);
                        lastRowIndex = writeOneRow(t, columns, sheet, lastRowIndex);
                    }
                }
            }
        }
    }

    /**
     * 多线程写入分页
     */
    private Future<Integer> multiThreadWritePage(
            final Long start, final PageExport<T> pageExport,
            final XSSFSheet sheet, final List<Column> columns,
            final AtomicInteger lastRowIndex) {
        Future<Integer> future = TASKEXECUTORSERVICE.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                List<T> pageDatas = pageExport.query(start);
                if (pageDatas != null && pageDatas.size() > 0) {
                    for (int i = 0; i < pageDatas.size(); i++) {
                        T t = pageDatas.get(i);
//                      sheet.createRow(number) 非线程安全所以需要加锁
                        synchronized (SimpleExcelExport.LOCK) {
                            writeOneRow(t, columns, sheet, lastRowIndex.getAndIncrement());
                            String msg = LoggerUtils.getLoggerMsg(MULTITHREADEXPORT_ONEROW_INFO, Thread.currentThread().getId(), lastRowIndex.get());
                            System.out.println(msg);
                            LOGGER.debug(msg);
                        }
//                        下面这段代码能早成系统死锁
//                        writeOneRow(t, columns, sheet, lastRowIndex.getAndIncrement());
//                        System.out.println(Thread.currentThread().getId() + "写" + lastRowIndex.get());
                    }
                }
                return pageDatas.size();
            }
        });
        return future;
    }

    @Override
    public void export(List<T> datas, File file) {
        XSSFWorkbook workBook = new XSSFWorkbook();
        XSSFSheet sheet = workBook.createSheet();

        //创建Excel头
        List<Column> headers = getHeaders();
        int lastRowIndex = createHeader(sheet, headers);
        if (lastRowIndex > 0) {
            writeList(datas, sheet, headers, lastRowIndex);
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try {
            OutputStream outputStream = FileUtils.openOutputStream(file);
            writeExcel(outputStream, workBook);
        } catch (IOException e) {
            LOGGER.error("写入文件异常 {}", file.getPath());
        }
    }

    @Override
    public void export(PageExport<T> pageExport, File file) {
        XSSFWorkbook workBook = new XSSFWorkbook();
        XSSFSheet sheet = workBook.createSheet();

        List<Column> headers = getHeaders();
        int lastRowIndex = createHeader(sheet, headers);
        if (lastRowIndex == 0) {
            return;
        }

        writePage(pageExport, sheet, lastRowIndex, headers);

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try {
            OutputStream outputStream = FileUtils.openOutputStream(file);
            writeExcel(outputStream, workBook);
        } catch (IOException e) {
            LOGGER.error("写入文件异常 {}", file.getPath());
        }
    }

    @Override
    public void export(List<T> datas, OutputStream ouputStream) {
        if (ouputStream == null) {
            return;
        }
        XSSFWorkbook workBook = new XSSFWorkbook();
        XSSFSheet sheet = workBook.createSheet();

        //创建Excel头
        List<Column> headers = getHeaders();
        int lastRowIndex = createHeader(sheet, headers);
        if (lastRowIndex > 0) {
            writeList(datas, sheet, headers, lastRowIndex);
        }
        writeExcel(ouputStream, workBook);
    }

    @Override
    public void export(PageExport<T> pageExport, OutputStream ouputStream) {
        XSSFWorkbook workBook = new XSSFWorkbook();
        XSSFSheet sheet = workBook.createSheet();

        //创建Excel头
        List<Column> headers = getHeaders();
        int lastRowIndex = createHeader(sheet, headers);
        if (lastRowIndex > 0) {
            writePage(pageExport, sheet, lastRowIndex, headers);
        }
        writeExcel(ouputStream, workBook);
    }

    @Override
    public void multiThreadExport(final PageExport<T> pageExport, OutputStream ouputStream) {
        XSSFWorkbook workBook = new XSSFWorkbook();
        final XSSFSheet sheet = workBook.createSheet();

        Integer totalWriteRecords = 0;
        List<Column> headers = getHeaders();
        int lastRowNum = createHeader(sheet, headers);
        if (lastRowNum > 0) {
            final AtomicInteger lastRowIndex = new AtomicInteger(lastRowNum);
            List<Future<Integer>> futures = new ArrayList<>();
            Long totalPages = pageExport.getTotalPage();
            if (totalPages != null && totalPages.longValue() > 0) {
                for (long page = 1; page <= totalPages.longValue(); page++) {
                    Long start = Long.valueOf((page - 1L) * pageExport.getPageSize());
                    futures.add(multiThreadWritePage(start, pageExport, sheet, headers, lastRowIndex));
                }
            }

            if (futures != null && futures.size() > 0) {
                for (Future<Integer> future : futures) {
                    try {
                        totalWriteRecords += future.get();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        writeExcel(ouputStream, workBook);
        LOGGER.info(MULTITHREADEXPORT_OVER_INFO, totalWriteRecords);
    }

    @Override
    public void multiThreadExport(final PageExport<T> pageExport, File file) {
        XSSFWorkbook workBook = new XSSFWorkbook();
        final XSSFSheet sheet = workBook.createSheet();

        Integer totalWriteRecords = 0;
        List<Column> headers = getHeaders();
        int lastRowNum = createHeader(sheet, headers);
        if (lastRowNum > 0) {
            final AtomicInteger lastRowIndex = new AtomicInteger(lastRowNum);
            List<Future<Integer>> futures = new ArrayList<>();
            Long totalPages = pageExport.getTotalPage();
            if (totalPages != null && totalPages.longValue() > 0) {
                for (long page = 1; page <= totalPages.longValue(); page++) {
                    Long start = Long.valueOf((page - 1L) * pageExport.getPageSize());
                    futures.add(multiThreadWritePage(start, pageExport, sheet, headers, lastRowIndex));
                }
            }

            if (futures != null && futures.size() > 0) {
                for (Future<Integer> future : futures) {
                    try {
                        totalWriteRecords += future.get();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try {
            OutputStream outputStream = FileUtils.openOutputStream(file);
            writeExcel(outputStream, workBook);
            LOGGER.info(MULTITHREADEXPORT_OVER_INFO, totalWriteRecords);
        } catch (IOException e) {
            LOGGER.error("写入文件异常 {}", file.getPath());
        }
    }
}
